package com.briup.logging.resolver;

import com.briup.logging.annotation.LoggingAccess;
import com.briup.logging.entity.LoggingAccessInfo;
import com.briup.logging.holder.LoggingAccessInfoHolder;
import com.briup.logging.persist.ILoggingAccessPersisting;
import com.briup.logging.util.AopUtil;
import com.briup.logging.util.ClassUtil;
import com.briup.logging.util.NetworkUtil;
import com.briup.logging.util.WebUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;

/**
 * 日志注解LoggingAccess的解析类
 */
@Slf4j
@Aspect
public class LoggingAccessAspectResolver {

    private ILoggingAccessPersisting loggingAccessPersisting;

    @Autowired
    private LoggingDetailHandler loggingDetailHandler;

    public LoggingAccessAspectResolver(ILoggingAccessPersisting loggingAccessPersisting) {
        this.loggingAccessPersisting = loggingAccessPersisting;
    }

//    @Pointcut("within(com.briup..web.contoller..*)")
//    @Pointcut("within(com.briup..web.contoller..*)")
    @Pointcut("execution(* com.briup..web.controller.*.*(..)) && @annotation(com.briup.logging.annotation.LoggingAccess)")
    public void loggingPoincut() {}

    // 环绕拦截器
    @Around("loggingPoincut()")
    public Object loggingAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        LoggingAccessInfo loggingAccessInfo = LoggingAccessInfoHolder.initial();

        MethodSignature methodSignature = ((MethodSignature) proceedingJoinPoint.getSignature());
        Method method = methodSignature.getMethod();

        //获取方法的签名，包含方法名和参数列表
        String methodName = AopUtil.getMethodName(proceedingJoinPoint);

        // target （被代理对象的类型）
        Class<?> target = proceedingJoinPoint.getTarget().getClass();

        //设置从LoggingAccess注解上获取的内容新
        //flag为true表示在类上 或者 方法上加了LoggingAccess注解
        boolean flag = loggingAnnotation(loggingAccessInfo, target, method);

        // 设置请求开始时间
        loggingAccessInfo.setRequestTime(new Date());
        // 默认设置请求成功
        loggingAccessInfo.setSuccessful(true);


        Object result = null;
        // 如果proceed方法抛了异常，那么就临时保存这个异常，最后再抛出去，否则全局异常处理不生效
        Exception exception = null;
        try {

            result = proceedingJoinPoint.proceed();

        } catch (Exception e) {
            exception = e;
            log.error("An exception[{}] occurred while saving the log", e.getMessage());
            loggingAccessInfo.setSuccessful(false);
            loggingAccessInfo.setErrorMessage(e.getMessage());
        }

        // 如果没有 在类上 或者 方法上任何一个地方加入LoggingAccess注解的话
        // 那么就直接返回，不做日志记录
        if(!flag) return result;

        // 设置响应时间
        loggingAccessInfo.setResponseTime(new Date());

        //设置本次请求耗时，单位：毫秒
        loggingAccessInfo.setUsingTime(String.valueOf(loggingAccessInfo.getResponseTime().getTime()-loggingAccessInfo.getRequestTime().getTime()));


        // 设置访问的类
        loggingAccessInfo.setDeclaredClassname(target.getName());
        // 设置访问的方法
        loggingAccessInfo.setDeclaredMethodname(methodName);

        // 设置请求头信息到日志中
        loggingHeaders(loggingAccessInfo);
        //设置当前用户的信息，将来使用者提供该方法实现
        loggingDetailHandler.loggingUserDeltail(loggingAccessInfo);

        // 持久化操作
        loggingAccessPersisting.persist(loggingAccessInfo);

        // 清理本次日志
        LoggingAccessInfoHolder.clear();

        // 最后判断本次请求是否成功，如果失败了 那么就抛出异常，以便全局异常处理
        if(!loggingAccessInfo.getSuccessful()){
            throw exception;
        }

        return result;

    }

    /**
     * 尽量请求头中的信息到日志中
     * @param loggingAccessInfo
     */
    private void loggingHeaders(LoggingAccessInfo loggingAccessInfo) {

        //获取请求对象
        HttpServletRequest request = WebUtil.getHttpServletRequest();

        if (request == null) {
            return;
        }

        //设置请求方式
        loggingAccessInfo.setRequestMethod(request.getMethod());


        //获取并设置请求中基本的头信息，并设置到日志中
        Map<String, String> headers = WebUtil.getHeaders(request);

        loggingAccessInfo.setReferer(headers.get("Referer"));//referer
        loggingAccessInfo.setUserAgent(headers.get("User-Agent"));//客户端标识
        loggingAccessInfo.setRequestUri(request.getRequestURI());//请求相对路径
        loggingAccessInfo.setClientIp(NetworkUtil.getIpAddress(request));//ip地址

        String xForwardedHost = headers.get("x-forwarded-host");
        String xForwardedPort = headers.get("x-forwarded-port");
        if (xForwardedHost != null) {
            loggingAccessInfo.setOriginalIp(xForwardedHost.concat(":").concat(xForwardedPort));//ip地址
        }
    }


    /**
     * 判断方法上或者类上是否使用LoggingAccess注解
     * 如果有使用，那么尝试获取LoggingAccess注解的value属性中，并设置到日志中
     * @param loggingAccessInfo
     * @param target
     * @param method
     * @return
     */
    private boolean loggingAnnotation(LoggingAccessInfo loggingAccessInfo, Class<?> target, Method method){

        //获取指定方法上的指定类型注解，会自动查找父类中方法
        LoggingAccess methodAnnotation = ClassUtil.getAnnotation(method, LoggingAccess.class);

        //获取指定类上的指定类型注解，会自动查找父类
        LoggingAccess classAnnotation = ClassUtil.getAnnotation(target, LoggingAccess.class);

        // LoggingAccess注解用在类上，value属性表示 访问的当前的【模块】名称
        if (classAnnotation != null) {
            loggingAccessInfo.setModular(classAnnotation.value());
        }

        // LoggingAccess注解用在方法上，value属性表示 访问的当前的【操作】名称
        if (methodAnnotation != null) {
            //设置注解中获取内容的，就是将来的日志内容
            loggingAccessInfo.setOperation(methodAnnotation.value());
        }

        return classAnnotation != null || methodAnnotation != null;

    }


}
